package service

import (
	"errors"
	"fmt"
	"gitee.com/sunny-zhangqiang/zcache/comm/mcache"
	"gitee.com/sunny-zhangqiang/zcache/service/forward"
	"io"
	"strconv"

	"gitee.com/sunny-zhangqiang/zcache/cache"
	"gitee.com/sunny-zhangqiang/zcache/comm"
	"gitee.com/sunny-zhangqiang/zcache/comm/logger"
	"gitee.com/sunny-zhangqiang/zcache/config/domain"
	"gitee.com/sunny-zhangqiang/zcache/service/base"
	"gitee.com/sunny-zhangqiang/zcache/xhttp"
)

func (srv *Service) buildCacheKey(req *base.Request) string {
	// TODO: 基于配置生成cacheKey, 当前仅返回url path&query
	return req.ClientReq.URL.Path + "?" + req.ClientReq.URL.Query().Encode()
}

func (srv *Service) doPurge(req *base.Request) {
	cache.Cache.Del(comm.String2Bytes(req.CacheKey))
}

// interruptConn 中断数据传输, 在golang http在chunk传输场景中, 没有中断响应的API.
// 在退出协程时, 默认数据发送完毕, 会发送最后的 end chunk.
// 仅限于H1协议
func (srv *Service) interruptConn(req *base.Request) {
	hj, ok := req.ClientRw.(xhttp.Hijacker)
	if !ok {
		logger.Error("rw is not Hijacker?")
		return
	}

	conn, _, err := hj.Hijack()
	if err != nil {
		logger.Error("do Hijack failed, err: %v", err)
		return
	}

	_ = conn.Close()
}

// TODO: 用于实现access log等, 暂未实现
func (srv *Service) logHandle(req *base.Request) {

}

// FIXME: 基于配置定制response Body.
func (srv *Service) doSpecialResponse(req *base.Request, status int, body string) {
	if req.HeaderSent {
		return
	}

	respHdr := req.ClientRw.Header()
	respHdr.Add(base.ContentLengthHdr, strconv.Itoa(len(body)))
	req.ClientRw.WriteHeader(status)
	req.HeaderSent = true
	req.ClientRw.Write(comm.String2Bytes(body))

	srv.logHandle(req)
}

func (srv *Service) doRawProxy(req *base.Request) {
	resp, err := forward.Forward(req)
	if err != nil {
		// FIXME: 基于err生成response code, 这里就直接使用502了.
		srv.doSpecialResponse(req, xhttp.StatusBadGateway, err.Error())
		return
	}

	defer resp.Body.Close()

	dstHdr := req.ClientRw.Header()
	base.CopyHeader(dstHdr, resp.Header)
	req.ClientRw.WriteHeader(resp.StatusCode)

	buf := mcache.Malloc(32 << 10)
	for {
		nr, err := resp.Body.Read(buf)
		if nr > 0 {
			nw, err := req.ClientRw.Write(buf[:nr])
			if err != nil {
				// TODO: 记录error clode
				req.Error = fmt.Errorf("write client failed, err: %v", err)
				srv.interruptConn(req)
				break
			}

			if nw != nr {
				// TODO: 记录error code
				req.Error = fmt.Errorf("nw not equal nr, nw: %d, nr: %d", nw, nr)
				srv.interruptConn(req)
				break
			}
		}

		if err != nil {
			if err == io.EOF {
				break
			}
			// 数据截断, 终止响应
			srv.interruptConn(req)
			break
		}
	}

	srv.logHandle(req)
}

func (srv *Service) doProxy(req *base.Request) {
	if req.ClientReq.Method != xhttp.MethodGet {
		srv.doRawProxy(req)
		return
	}
}

func (srv *Service) ServeHTTP(rw xhttp.ResponseWriter, r *xhttp.Request) {
	req := &base.Request{
		ClientRw:  rw,
		ClientReq: r,
	}

	host := r.Host
	path := r.URL.Path
	conf, err := domain.LookupDomainPathConf(host, path)
	if err != nil {
		logger.Warn("lookup domain conf path failed, err: %v", err)
		if err == domain.ErrNotFound {
			req.Error = errors.New("domain conf not found")
			srv.doSpecialResponse(req, xhttp.StatusForbidden, "not found domain conf")
			return
		}

		req.Error = fmt.Errorf("get domain conf failed, err: %v", err)
		srv.doSpecialResponse(req, xhttp.StatusInternalServerError, err.Error())
		return
	}

	req.Conf = conf

	cacheKey := req.ClientReq.Header.Get(base.CacheKey)
	if cacheKey == "" {
		cacheKey = srv.buildCacheKey(req)
	}
	req.CacheKey = cacheKey

	if req.ClientReq.Method == xhttp.MethodPurge {
		srv.doPurge(req)
		srv.doSpecialResponse(req, xhttp.StatusOK, "success")
		return
	}

	srv.doProxy(req)
}
